#include "buildPrimitive.h"
#include "buildTransform.h"

#include <core/spectrum.h>
#include <core/shape.h>

#include <shapes/sphere.h>
#include <shapes/cone.h>
#include <shapes/trianglemesh.h>

#include <iostream>

//TODO:
#include "core/material.h"
#include "materials/matte.h"


Sphere *getSphere( FnKat::Render::RenderSettings &renderSettings,
                   FnKat::FnScenegraphIterator sgIt,
                   FnKat::GroupAttribute geoAttr,
                   Transform *obj2world,
                   Transform *world2obj )
{
    ParamSet p;
    setDoubleParam(p, geoAttr, "radius", "radius");

    return CreateSphereShape(obj2world, world2obj, false, p);
}


Cone *getCone( FnKat::Render::RenderSettings &renderSettings, FnKat::FnScenegraphIterator sgIt,
               FnKat::GroupAttribute geoAttr,
               Transform *obj2world, Transform *world2obj )
{
    ParamSet p;
    setDoubleParam(p, geoAttr, "radius", "radius");
    setDoubleParam(p, geoAttr, "height", "height");
    setDoubleParam(p, geoAttr, "phimax", "phimax");

    return CreateConeShape(obj2world, world2obj, false, p);
}


TriangleMesh *getTriangleMesh( FnKat::Render::RenderSettings &renderSettings,
                               FnKat::FnScenegraphIterator sgIt,
                               FnKat::GroupAttribute geoAttr,
                               Transform *obj2world,
                               Transform *world2obj)
{
    ParamSet p;

    // Check the attributes

    // Vertex positions (P)
    FnKat::GroupAttribute pointAttr = geoAttr.getChildByName("point");
    if (!pointAttr.isValid())
    {
        cerr << "No geometry.point in " << sgIt.getFullName() << endl;
        return NULL;
    }

    // Vertex indices and start indices
    FnKat::GroupAttribute polyAttr = geoAttr.getChildByName("poly");
    if (!polyAttr.isValid())
    {
        cerr << "No geometry.poly in " << sgIt.getFullName() << endl;
        return NULL;
    }

    FnKat::IntAttribute vertexListAttr = polyAttr.getChildByName("vertexList");
    if (!vertexListAttr.isValid() )
    {
        cerr << "No geometry.poly.vertexList in " << sgIt.getFullName() << endl;
        return NULL;
    }

    FnKat::IntAttribute startIndexAttr = polyAttr.getChildByName("startIndex");
    if (!startIndexAttr.isValid() )
    {
        cerr << "No geometry.poly.startIndex in " << sgIt.getFullName() << endl;
        return NULL;
    }


    // Create the parameters

    //P
    FnKat::FloatAttribute pAttr = pointAttr.getChildByName("P");
    if(!pAttr.isValid())
    {
        cerr << "No geometry.points.P in " << sgIt.getFullName() << endl;
        return NULL;
    }

    FnKat::FloatConstVector pVec = pAttr.getNearestSample(0.0f);
    int nPoints = pVec.size();
    Point *points = new Point[nPoints];
    for(int i = 0, pi = 0; i < nPoints; ++pi)
    {
        points[pi].x = pVec[i++];
        points[pi].y = pVec[i++];
        points[pi].z = pVec[i++];
    }

    p.AddPoint("P", points, nPoints);
    delete(points);

    //indices
    FnKat::IntConstVector vertexList = vertexListAttr.getNearestSample(0.0f);
    FnKat::IntConstVector startIndices = startIndexAttr.getNearestSample(0.0f);

    int numPolys = startIndices.size() - 1;
    int numTriangles = vertexList.size() - 2 * numPolys;
    int *indices = new int[numTriangles * 3];

    int pos = 0;
    for(int poly = 0; poly < numPolys; poly++)
    {

        //first and last vertex indexes in the poly
        int firstIndex = startIndices[poly];
        int lastIndex  = startIndices[poly + 1] - 1;

        int v1 = vertexList[firstIndex];
        int v2;
        int v3;

        for(int i = firstIndex; i < lastIndex - 1; ++i)
        {
            v2 = vertexList[i + 1];
            v3 = vertexList[i + 2];

            indices[pos++] = v1;
            indices[pos++] = v2;
            indices[pos++] = v3;
        }
    }

    p.AddInt("indices", indices, pos);
    delete[] indices;

    //TODO: normals & uvs

    return CreateTriangleMeshShape(obj2world, world2obj, false, p);
}


GeometricPrimitive *getPrimitive( FnKat::Render::RenderSettings &renderSettings,
                                  FnKat::FnScenegraphIterator sgIt )
{
    string type = sgIt.getType();

    if(type != "sphere" && type != "cone" && type != "polymesh" && type != "subdmesh")
    {
        return NULL;
    }

    // Material
    ParamSet p;
    map<string, Reference<Texture<float> > > floatTextures;
    map<string, Reference<Texture<Spectrum> > > spectrumTextures;
    TextureParams tp(p, p, floatTextures, spectrumTextures);
    Transform mtl2world;
    Reference<Material> mtl = CreateMatteMaterial(mtl2world, tp);

    //Transform
    Transform *obj2world;
    obj2world = getWorldToObjTransform(renderSettings, sgIt);
    Transform *world2obj = new Transform(Inverse(*obj2world));


    // the geometry attr
    FnKat::GroupAttribute geoAttr = sgIt.getAttribute("geometry");

    // Shape
    Reference<Shape> shape = NULL;
    if(type == "sphere")
    {
        shape = getSphere(renderSettings, sgIt, geoAttr, obj2world, world2obj);
    }
    else if(type == "cone")
    {
        shape = getCone(renderSettings, sgIt, geoAttr, obj2world, world2obj);
    }
    else if(type == "polymesh" || type == "subdmesh")
    {
        shape = getTriangleMesh(renderSettings, sgIt, geoAttr, obj2world, world2obj);
    }

    if( shape == NULL )
    {
        return NULL;
    }

    return new GeometricPrimitive(shape, mtl, NULL);
}


int getPrimitives( FnKat::Render::RenderSettings &renderSettings,
                   FnKat::FnScenegraphIterator sgIt,
                   vector<Reference<Primitive> > &primitives )
{
    int primitivesCount = 0;
    GeometricPrimitive *prim = NULL;
    string type = sgIt.getType();

    if(type == "group")
    {
        FnKat::FnScenegraphIterator childIt;
        childIt = sgIt.getFirstChild();

        while(childIt.isValid())
        {
            primitivesCount += getPrimitives(renderSettings, childIt, primitives);
            childIt = childIt.getNextSibling();
        }
    }
    else
    {
        prim = getPrimitive(renderSettings, sgIt);
        if(prim)
        {
            primitives.push_back(prim);
            primitivesCount++;
        }
    }

    return primitivesCount;
}

